
#include "sysclk.h"
#include "uotghs_otg.h"
#include "uotghs_device.h"
#include <string.h>
#include <stdlib.h>

bool (*setup_process_ptr) (void);
usb_setup_req_t g_setup_req;
uint8_t *ep0_buf_ptr = NULL;
uint16_t max_ctrl_trans_length;
uint16_t g_device_status = 0;
uint16_t g_ep_status = 0;
uint8_t g_config = 0;
uint8_t g_interface_num = 1;
uint8_t g_alter_setting = 0;
uint32_t tmp[2] = {0};

void udd_disable(void)
{
	irqflags_t flags;

	flags = cpu_irq_save();
	otg_unfreeze_clock();
	udd_detach_device();
	otg_freeze_clock();

	otg_disable();
	otg_disable_pad();
	sysclk_disable_usb();
	pmc_disable_periph_clk(ID_UOTGHS);
	// Else the USB clock disable is done by UHC which manage USB dual role
	cpu_irq_restore(flags);
}

void udd_enable(void)
{
	irqflags_t flags;

	setup_process_ptr = NULL;
	flags = cpu_irq_save();

	pmc_enable_periph_clk(ID_UOTGHS);
	sysclk_enable_usb();

	ex_status.usb_role = USB_DEVICE; 

	//set interrupt vector, %by Santiago
	NVIC_SetPriority((IRQn_Type) ID_UOTGHS, 1);
	NVIC_EnableIRQ((IRQn_Type) ID_UOTGHS);
	PMC->PMC_FSMR |= (PMC_FSMR_USBAL & PMC_FAST_STARTUP_Msk);

	// ID pin not used then force device mode
	Clr_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_UIDE); 
	Set_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_UIMOD);

	// Enable USB hardware
	Set_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_OTGPADE);
	Set_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_USBE);

	// Set the USB speed requested by configuration file
	Clr_bits(UOTGHS->UOTGHS_DEVCTRL, UOTGHS_DEVCTRL_LS);  

	//udd_high_speed_enable();
	Wr_bitfield(UOTGHS->UOTGHS_DEVCTRL, UOTGHS_DEVCTRL_SPDCONF_Msk, 0);

	// Check USB clock
	Clr_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_FRZCLK);
	while (!Is_otg_clock_usable());  

	// Reset internal variables
	otg_ack_vbus_transition();
	// Force Vbus interrupt in case of Vbus always with a high level
	// This is possible with a short timing between a Host mode stop/start.
	if (Is_otg_vbus_high()) 
	{
		otg_raise_vbus_transition();
	}
	otg_enable_vbus_interrupt();
	otg_freeze_clock();            

	cpu_irq_restore(flags);       
}

void udd_reset_ep_ctrl(void)
{
	irqflags_t flags;

	// Reset USB address to 0
	Wr_bitfield(UOTGHS->UOTGHS_DEVCTRL, UOTGHS_DEVCTRL_UADD_Msk, 0);

	//udd_enable_address();
	Set_bits(UOTGHS->UOTGHS_DEVCTRL, UOTGHS_DEVCTRL_ADDEN);

	// Alloc and configure control endpoint
	udd_configure_endpoint(0,
		USB_EP_TYPE_CONTROL,
		0,
		USB_DEVICE_EP_CTRL_SIZE,
		UOTGHS_DEVEPTCFG_EPBK_1_BANK);

	udd_allocate_memory(0);
	udd_enable_endpoint(0);
	flags = cpu_irq_save();        
	udd_enable_setup_received_interrupt(0);
	udd_enable_endpoint_interrupt(0);
	cpu_irq_restore(flags);
}

void udd_ctrl_init(void)
{
	irqflags_t flags;
	flags = cpu_irq_save();

	// In case of abort of IN Data Phase:
	// No need to abort IN transfer (rise TXINI),
	// because it is automatically done by hardware when a Setup packet is received.
	// But the interrupt must be disabled to don't generate interrupt TXINI
	// after SETUP reception.
	udd_disable_in_send_interrupt(0);

	// By default disable overflow and underflow interrupt
	//udd_disable_nak_in_interrupt(0);
	//udd_disable_nak_out_interrupt(0);

	cpu_irq_restore(flags);

	// In case of OUT ZLP event is no processed before Setup event occurs
	udd_ack_out_received(0);
}

void udd_ctrl_setup_received(void)
{
	uint8_t i;
	uint8_t *ptr = NULL;
	uint8_t rev_count;

	// Fill setup request structure
	rev_count = udd_byte_count(0);
	if (8 != rev_count) {
		//udd_ctrl_stall_data();
		//udd_ep_control_state = UDD_EPCTRL_STALL_REQ;
		udd_enable_stall_handshake(0);

		udd_ack_setup_received(0);
		return; // Error data number doesn't correspond to SETUP packet
	}

	ptr = (uint8_t *) & udd_get_endpoint_fifo_access(0,8);
	for(i =0; i < 8; i++)
	{
		((uint8_t*)(&(g_setup_req)))[i] = *ptr++;
	}

	// Decode setup request
	if(setup_process_ptr != NULL)
	{
		if (setup_process_ptr() == false) 
		{
			// Setup request unknow then stall it
			udd_enable_stall_handshake(0);         
			udd_ack_setup_received(0);
			return;
		}
	}

	//setup-cmd packet can be ack
	udd_ack_setup_received(0);

	if((g_setup_req.bmRequestType & USB_REQ_DIR_MASK) == USB_REQ_DIR_IN)
	{
		udd_ctrl_send();
	}
	else
	{
		udd_ctrl_receive();
		if(g_setup_req.bRequest == USB_REQ_SET_ADDRESS)
		{
			Clr_bits(UOTGHS->UOTGHS_DEVCTRL, UOTGHS_DEVCTRL_ADDEN);
			Wr_bitfield(UOTGHS->UOTGHS_DEVCTRL, UOTGHS_DEVCTRL_UADD_Msk, (g_setup_req.wValue & 0x7f));
			Set_bits(UOTGHS->UOTGHS_DEVCTRL, UOTGHS_DEVCTRL_ADDEN);

			ex_status.usb_role = USB_ADDRESSED;
		}
	}       
}

/*void udd_ep_send(uint8_t ep_num, uint8_t *buff_ptr, uint32_t length, uint32_t pkt_len)
{
uint32_t trans_length;
uint8_t* dst_ptr = NULL;
uint8_t* src_ptr = NULL;
uint16_t i;

if(buff_ptr != NULL)
{
src_ptr = buff_ptr;
}

dst_ptr = (uint8_t *) & udd_get_endpoint_fifo_access(ep_num, 8);

if(length == 0)
{
//send in pkt
while(!Is_udd_in_send(ep_num));
udd_ack_in_send(ep_num);
udd_ack_fifocon(ep_num);

return;       
}

while(length > 0)
{
//wait for in pkt
while(!Is_udd_in_send(ep_num));

if(length > pkt_len)
{
trans_length = pkt_len;
}
else
{
trans_length = length;
}

for(i = 0; i < trans_length; i++)
{
*dst_ptr++ = *src_ptr++;
}

//send in pkt
udd_ack_in_send(ep_num);         
udd_ack_fifocon(ep_num); 

//update length
length -= trans_length;
}
}*/

/*bool udd_ep_receive(uint8_t ep_num, uint8_t *buff_ptr, uint32_t total_length, uint32_t valid_length, uint32_t pkt_len)
{
uint32_t trans_length = 0;
uint32_t nb_data = 0;
uint8_t* dst_ptr = NULL;
uint8_t* src_ptr = NULL;
uint16_t i;

if(buff_ptr != NULL)
{
dst_ptr = buff_ptr;
}   

while(total_length > 0)
{
if(total_length > pkt_len)
{
trans_length = pkt_len;
}
else
{
trans_length = total_length;
}

while(!Is_udd_out_received(ep_num)); 
src_ptr = (uint8_t *) & udd_get_endpoint_fifo_access(ep_num, 8);  

nb_data = udd_byte_count(ep_num);
if(nb_data != trans_length)
{
return false;
//trans_length = nb_data;
}

if(valid_length > 0)
{
for(i = 0; i < trans_length; i++)
{
*dst_ptr++ = *src_ptr++;
valid_length--;
if(valid_length == 0)
{
break;
}
}
}        
udd_ack_out_received(ep_num);
udd_ack_fifocon(ep_num);

//update datalength
total_length -= trans_length;
}

return true;
}*/

bool udd_ep_receive_one_pkt(uint8_t ep_num, uint8_t *buff_ptr, uint32_t *rev_len)
{
	uint32_t nb_data = 0;
	uint8_t* dst_ptr = NULL;
	uint8_t* src_ptr = NULL;
	uint32_t i;

	if(buff_ptr != NULL)
	{
		dst_ptr = buff_ptr;
	}  

	if(!Is_udd_out_received(ep_num))
	{
		return false;
	}
	src_ptr = (uint8_t *) & udd_get_endpoint_fifo_access(ep_num, 8); 

	nb_data = udd_byte_count(ep_num);

	for(i = 0; i < nb_data; i++)
	{
		*dst_ptr++ = *src_ptr++;
	}

	udd_ack_out_received(ep_num);
	udd_ack_fifocon(ep_num);

	*rev_len = nb_data;

	return true;
}

bool udd_ep_send_one_pkt(uint8_t ep_num, uint8_t *buff_ptr, uint16_t length)
{
	uint8_t* dst_ptr = NULL;
	uint8_t* src_ptr = NULL;
	uint16_t i;

	/*if(length == 0)
	{
	//send in pkt
	while(!Is_udd_in_send(ep_num));
	udd_ack_in_send(ep_num);
	udd_ack_fifocon(ep_num);

	return;       
	}*/

	//wait for in pkt
	if(!Is_udd_in_send(ep_num))
	{
		return false;
	}

	if(buff_ptr != NULL)
	{
		src_ptr = buff_ptr;
	}

	dst_ptr = (uint8_t *) & udd_get_endpoint_fifo_access(ep_num, 8);       

	for(i = 0; i < length; i++)
	{
		*dst_ptr++ = *src_ptr++;
	}  

	//send in pkt
	udd_ack_in_send(ep_num);
	udd_ack_fifocon(ep_num);

	return true;
}

void udd_ep_send_dma(uint8_t ep_num, uint8_t *buff_ptr, uint32_t length, uint32_t pkt_len)
{
	uint32_t udd_dma_ctrl = 0;
	bool b_shortpackt = false;

	if(length == 0)
	{
		//send in pkt
		while(!Is_udd_in_send(ep_num));
		udd_ack_in_send(ep_num);
		udd_ack_fifocon(ep_num);

		return;       
	}

	while(!Is_udd_in_send(ep_num));

	if(0 != (length % pkt_len))
	{
		b_shortpackt = true;       
	}

	udd_dma_ctrl = UOTGHS_DEVDMACONTROL_BUFF_LENGTH(length);

	udd_endpoint_dma_set_addr(ep_num, (uint32_t)(buff_ptr));
	udd_dma_ctrl |= UOTGHS_DEVDMACONTROL_CHANN_ENB;
	udd_endpoint_dma_set_control(ep_num, udd_dma_ctrl);

	while(1)
	{
		if(!(udd_endpoint_dma_get_status(ep_num) & UOTGHS_DEVDMASTATUS_CHANN_ENB))
		{
			if(!(udd_endpoint_dma_get_status(ep_num) & UOTGHS_DEVDMASTATUS_CHANN_ACT))
			{
				break;
			}
		}
	}


	if(b_shortpackt)
	{
		udd_ack_in_send(ep_num);   
		udd_ack_fifocon(ep_num);
	}
}

bool udd_ep_send_dma_direct(uint8_t ep_num, uint8_t *buff_ptr, uint32_t length)
{
	uint32_t udd_dma_ctrl = 0;

	udd_dma_ctrl = UOTGHS_DEVDMACONTROL_BUFF_LENGTH(length);      
	udd_endpoint_dma_set_addr(ep_num, (uint32_t)(buff_ptr));
	udd_dma_ctrl |= UOTGHS_DEVDMACONTROL_CHANN_ENB;

	udd_endpoint_dma_set_control(ep_num, udd_dma_ctrl);

	return true;
}

bool udd_ep_receive_dma_direct(uint8_t ep_num, uint8_t *buff_ptr, uint32_t total_length)
{
	uint32_t udd_dma_ctrl = 0;

	udd_dma_ctrl = UOTGHS_DEVDMACONTROL_BUFF_LENGTH(total_length);

	udd_endpoint_dma_set_addr(ep_num, (uint32_t)(buff_ptr));
	udd_dma_ctrl |= UOTGHS_DEVDMACONTROL_CHANN_ENB;

	udd_endpoint_dma_set_control(ep_num, udd_dma_ctrl);

	return true;
}

void wait_for_usb_dma(uint8_t ep_num, bool b_dir_in)
{
	while(1)
	{
		if(!(udd_endpoint_dma_get_status(ep_num) & UOTGHS_DEVDMASTATUS_CHANN_ENB))
		{
			if(!(udd_endpoint_dma_get_status(ep_num) & UOTGHS_DEVDMASTATUS_CHANN_ACT))
			{
				break;
			}
		}
	}

	if(!b_dir_in)
	{
		udd_ack_out_received(ep_num);
	}        
}

bool udd_ep_receive_dma(uint8_t ep_num, uint8_t *buff_ptr, uint32_t total_length, uint32_t pkt_len)
{
	uint32_t udd_dma_ctrl = 0;

	while(!Is_udd_out_received(ep_num)); 
	udd_dma_ctrl = UOTGHS_DEVDMACONTROL_BUFF_LENGTH(total_length);

	if(total_length < pkt_len)
	{
		udd_dma_ctrl |= UOTGHS_DEVDMACONTROL_END_TR_EN;
	}

	udd_endpoint_dma_set_addr(ep_num, (uint32_t)(buff_ptr));
	udd_dma_ctrl |= UOTGHS_DEVDMACONTROL_CHANN_ENB;

	udd_endpoint_dma_set_control(ep_num, udd_dma_ctrl);

	while(1)
	{
		if(!(udd_endpoint_dma_get_status(ep_num) & UOTGHS_DEVDMASTATUS_CHANN_ENB))
		{
			if(!(udd_endpoint_dma_get_status(ep_num) & UOTGHS_DEVDMASTATUS_CHANN_ACT))
			{
				break;
			}
		}
	}

	udd_ack_out_received(ep_num);

	return true;
}

void udd_ctrl_receive(void)
{
	uint16_t data_length = max_ctrl_trans_length;

	if(data_length == 0)
	{
		//wait for IN ZLP be sent
		while(!Is_udd_in_send(0));
		//send in ZLP
		udd_ack_in_send(0);
		while(!Is_udd_in_send(0));
	}
}

void udd_ctrl_send(void)
{
	uint16_t data_length = max_ctrl_trans_length;
	uint16_t trans_length;
	uint8_t* dst_ptr = NULL;
	uint8_t* src_ptr = NULL;
	uint16_t i;

	if(ep0_buf_ptr != NULL)
	{
		src_ptr = ep0_buf_ptr;
	}

	//set dest to ep0 fifo
	dst_ptr = (uint8_t *) & udd_get_endpoint_fifo_access(0, 8);

	while(data_length > 0)
	{
		while(!Is_udd_in_send(0));
		if(data_length > USB_DEVICE_EP_CTRL_SIZE)
		{
			trans_length = USB_DEVICE_EP_CTRL_SIZE;
		}
		else
		{
			trans_length = data_length;
		}

		for(i = 0; i < trans_length; i++)
		{
			*dst_ptr++ = *src_ptr++;
		}

		//send in pkt
		udd_ack_in_send(0);

		//update datalength
		data_length -= trans_length;
	}

	//wait for an out ZLP
	udd_ack_out_received(0);
}

void udd_set_ep(uint8_t ep, uint8_t dir_in, uint8_t ep_type, uint16_t ep_size)
{
	uint8_t bank = 0;

	if(ep_type == USB_EP_TYPE_INTERRUPT)
	{
		bank = UOTGHS_DEVEPTCFG_EPBK_1_BANK >> UOTGHS_DEVEPTCFG_EPBK_Pos;
	}
	else if(ep_type == USB_EP_TYPE_BULK)
	{
		if((ep == 1) || (ep == 2))
		{
			bank = UOTGHS_DEVEPTCFG_EPBK_2_BANK >> UOTGHS_DEVEPTCFG_EPBK_Pos;
		}
		else
		{
			bank = UOTGHS_DEVEPTCFG_EPBK_1_BANK >> UOTGHS_DEVEPTCFG_EPBK_Pos;
		}
	}
	else
	{
		return;
	}

	udd_configure_endpoint(ep, ep_type, dir_in, ep_size, bank);

	udd_allocate_memory(ep);
	udd_enable_endpoint(ep);        

	if((ep == 1) || (ep == 2))
	{       
		udd_enable_endpoint_bank_autoswitch(ep);
	}
	if(dir_in == 0)
	{
		udd_enable_nyet_handshake(ep);
	}
}

void udd_clr_ep(uint8_t ep)
{    
	udd_disable_endpoint(ep);
	udd_unallocate_memory(ep);
}

void udd_close_all_ep_interrupt(void)
{
	uint8_t i;
	for(i = 1; i < USB_DEVICE_MAX_EP; i++)
	{
		udd_disable_endpoint_interrupt(i);
	}    
}


